﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MudEngine.Parsing.AST
{
    public enum BinaryOperatorType
    {
        Any = 0,
        BitwiseAnd = 1,
        BitwiseOr = 2,
        ConditionalAnd = 3,
        ConditionalOr = 4,
        ExclusiveOr = 5,
        GreaterThan = 6,
        GreaterThanOrEqual = 7,
        Equality = 8,
        InEquality = 9,
        LessThan = 10,
        LessThanOrEqual = 11,
        Add = 12,
        Subtract = 13,
        Multiply = 14,
        Divide = 15,
        Modulus = 16,
        ShiftLeft = 17,
        ShiftRight = 18,
        NullCoalescing = 19,
    }

    public class BinaryExpression : Expression
    {
		public const ResultType ALLOWED_TYPES = ResultType.SHORT | ResultType.INT | ResultType.LONG;
        public const ResultType ALLOWED_TYPES_ADD = ResultType.SHORT | ResultType.INT | ResultType.LONG | ResultType.REAL | ResultType.STRING | ResultType.OBJECT | ResultType.ARRAY | ResultType.VAR | ResultType.MAP;
		public const ResultType ALLOWED_TYPES_ARIT = ResultType.SHORT | ResultType.INT | ResultType.LONG | ResultType.REAL;
		public const ResultType ALLOWED_TYPES_COMPARE = ResultType.SHORT | ResultType.INT | ResultType.LONG | ResultType.REAL | ResultType.STRING | ResultType.BOOL | ResultType.VAR;

		public Expression Left { get; set; }
        public BinaryOperatorType Operator { get; set; }
        public Expression Right { get; set; }

		public static string OperatorName(BinaryOperatorType op)
		{
			switch (op)
			{
				case BinaryOperatorType.BitwiseAnd: return "&";
				case BinaryOperatorType.BitwiseOr: return "|";
				case BinaryOperatorType.ConditionalAnd: return "&&";
				case BinaryOperatorType.ConditionalOr: return "||";
				case BinaryOperatorType.ExclusiveOr: return "^";
				case BinaryOperatorType.GreaterThan: return ">";
				case BinaryOperatorType.GreaterThanOrEqual: return ">=";
				case BinaryOperatorType.Equality: return "==";
				case BinaryOperatorType.InEquality: return "!=";
				case BinaryOperatorType.LessThan: return "<";
				case BinaryOperatorType.LessThanOrEqual: return "<=";
				case BinaryOperatorType.Add: return "+";
				case BinaryOperatorType.Subtract: return "-";
				case BinaryOperatorType.Multiply: return "*";
				case BinaryOperatorType.Divide: return "/";
				case BinaryOperatorType.Modulus: return "%";
				case BinaryOperatorType.ShiftLeft: return "<<";
				case BinaryOperatorType.ShiftRight: return ">>";
			}
			return "illegal operator";
		}

        public BinaryExpression(Expression left, BinaryOperatorType op, Expression right)
        {
			this.Left = left; this.Left.Parent = this;
            this.Operator = op;
			this.Right = right; this.Right.Parent = this;
        }

		public CheckExpressionResult CheckExpressions()
		{
            if (this.Left.Type == null)
                return CheckExpressionResult.LEFT;
            else if (this.Right.Type == null)
                return CheckExpressionResult.RIGHT;

			switch (this.Operator)
			{
				case BinaryOperatorType.Add:
					if (!ALLOWED_TYPES_ADD.HasFlag(this.Left.Type.SystemType))
						return CheckExpressionResult.LEFT;
					else if (!ALLOWED_TYPES_ADD.HasFlag(this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT;
					else if (!CompatibleTypes(COMPATIBLES_ADD, this.Left.Type.SystemType, this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT | CheckExpressionResult.LEFT;
					break;
				case BinaryOperatorType.ShiftLeft:
				case BinaryOperatorType.ShiftRight:
				case BinaryOperatorType.BitwiseAnd:
				case BinaryOperatorType.BitwiseOr:
					if (!ALLOWED_TYPES.HasFlag(this.Left.Type.SystemType))
						return CheckExpressionResult.LEFT;
					else if (!ALLOWED_TYPES.HasFlag(this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT;
					break;
				case BinaryOperatorType.Modulus:
				case BinaryOperatorType.Multiply: 
				case BinaryOperatorType.Subtract: 
				case BinaryOperatorType.Divide:
					if (!ALLOWED_TYPES_ARIT.HasFlag(this.Left.Type.SystemType))
						return CheckExpressionResult.LEFT;
					else if (!ALLOWED_TYPES_ARIT.HasFlag(this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT;
					else if (!CompatibleTypes(COMPATIBLES_COMPARE, this.Left.Type.SystemType, this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT | CheckExpressionResult.LEFT;
					break;

				case BinaryOperatorType.ConditionalAnd:
				case BinaryOperatorType.ConditionalOr:
				case BinaryOperatorType.ExclusiveOr:
					if (!ALLOWED_TYPES.HasFlag(this.Left.Type.SystemType))
						return CheckExpressionResult.LEFT;
					else if (!ALLOWED_TYPES.HasFlag(this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT;
					break;
				case BinaryOperatorType.Equality:
				case BinaryOperatorType.InEquality:
				case BinaryOperatorType.GreaterThan:
				case BinaryOperatorType.GreaterThanOrEqual:
				case BinaryOperatorType.LessThan:
				case BinaryOperatorType.LessThanOrEqual:
					if (!ALLOWED_TYPES_COMPARE.HasFlag(this.Left.Type.SystemType))
						return CheckExpressionResult.LEFT;
					else if (!ALLOWED_TYPES_COMPARE.HasFlag(this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT;
					else if (!CompatibleTypes(COMPATIBLES_COMPARE, this.Left.Type.SystemType, this.Right.Type.SystemType))
						return CheckExpressionResult.RIGHT | CheckExpressionResult.LEFT;
					break;
			}
			return CheckExpressionResult.OK;
		}

        public override void AcceptVisitor<C>(IAstNodeVisitor<C> visitor, C ctx)
        {
            visitor.VisitBinaryExpression(this, ctx);
        }
    }
}
